package org.obsidianrad.utils;


import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.obsidianrad.server.services.ObsModel;

/**
 * 
 * @author Eligio Colutta
 * @version $Rev: 89 $
 * @lastautorcommit $Author: eliosh $
 * @lastdatecommit $Date: 2010-02-01 12:21:34 +0100(lun, 01 feb 2010) $
 */
public class TokenFactory {

	private static final Random rnd = new Random();
	private static final TokenFactory instance = new TokenFactory();
	private static final int THRESHOLD_IN_SECS = 120; // vita massima del token in secondi
	
	@SuppressWarnings("unchecked")
	private Map<String, Map<String, Token>> tokens;
	
	@SuppressWarnings("unchecked")
	private TokenFactory(){
		tokens = new HashMap<String, Map<String,Token>>();
	}
	
	public static TokenFactory getInstance(){
		return instance;
	}
	
	@SuppressWarnings("unchecked")
	public synchronized <T extends ObsModel> Token<T> getToken(T reference){
		String className = reference.getClass().getCanonicalName();
		Map<String,Token> tokenByClassName = null;
		if(tokens.containsKey(className)){
			tokenByClassName = tokens.get(className);
		}else{
			tokenByClassName = new HashMap<String, Token>();
			tokens.put(className, tokenByClassName);
		}
		String pKey = reference.getPKeyValue();
		if(pKey==null){
			// se non ha una primary key, non deve poter essere possibile bloccarlo.
			return null;
		}else if(tokenByClassName.containsKey(pKey)){
			// se è già presente nella lista, dobbiamo controllarne la "data di scadenza"
			Token<T> t = tokenByClassName.get(pKey);
			if(isTokenExpired(t)){
				// il token presente è scaduto. Eliminiamo il vecchio e creiamone un altro.
				tokenByClassName.remove(pKey);
				t = new Token<T>(reference, rnd.nextLong(), pKey);
				tokenByClassName.put(pKey, t);
				return t;				
			}else{
				// se il token già esistente è ancora valido, restituiamo un token vuoto.
				return null;
			}
		}else{
			// è valida la richiesta di blocco
			Token<T> t = new Token<T>(reference, rnd.nextLong(), pKey);
			tokenByClassName.put(pKey, t);
			return t;
		}
	}
	
	@SuppressWarnings("unchecked")
	public <T extends ObsModel> void discardToken(Token<T> token){
		String className = getTokenClassName(token); //token.getClassName();
		Map<String,Token> tokenByClassName = null;
		if(tokens.containsKey(className)){
			tokenByClassName = tokens.get(className);
		}else{
			// se non esiste una lista per questo tipo di oggetti bloccabili, usciamo dalla funzione senza errori.
			return;
		}
		String pKey = token.getPKey();
		Token tokenInList = tokenByClassName.get(pKey);
		if(tokenInList != null){
			if(tokenInList.getRandomKey() == token.getRandomKey()){
				// il token che ci è stato richiesto di cancellare è effettivamente presente nella nostra lista. Eliminiamolo.
				tokenByClassName.remove(pKey);
			}
		}
	}

	@SuppressWarnings("unchecked")
	public <T extends ObsModel> boolean isTokenValid(Token<T> token){
		if(token==null){
			return false;
		}
		String className = getTokenClassName(token); //token.getClassName();
		Map<String,Token> tokenByClassName = null;
		if(tokens.containsKey(className)){
			tokenByClassName = tokens.get(className);
		}else{
			// se non esiste una lista per questo tipo di oggetti bloccabili, il token non è valido.
			return false;
		}
		Token tokenInList = tokenByClassName.get(token.getPKey());
		if(tokenInList==null){
			// non esiste un token nella lista di questa classe con questa primary key
			return false;
		}
		if(tokenInList.getRandomKey() == token.getRandomKey()){
			// il token che ci è stato richiesto di validare è effettivamente presente nella nostra lista. 
			return true;
		}
		return false;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends ObsModel> Token<T> refreshToken(Token<T> token){
		String className = getTokenClassName(token); //token.getClassName();
		Map<String,Token> tokenByClassName = null;
		if(tokens.containsKey(className)){
			tokenByClassName = tokens.get(className);
		}else{
			// se non esiste una lista per questo tipo di oggetti bloccabili, il token non è valido.
			return null;
		}
		Token tokenInList = tokenByClassName.get(token.getPKey());
		if(tokenInList==null){
			// non esiste un token nella lista di questa classe con questa primary key
			return null;
		}
		tokenInList.setLastDate(System.currentTimeMillis());
		
		return tokenInList;
	}
	
	private <T extends ObsModel> boolean isTokenExpired(Token<T> token){
		long timeElapsed = System.currentTimeMillis() - token.getLastDate();
		return (timeElapsed > THRESHOLD_IN_SECS*1000);
	}
	
	private <T extends ObsModel> String getTokenClassName(Token<T> reference){
		T clazz = reference.getClazz();
		return clazz.getClass().getCanonicalName();
	}
}
